/*
 *  Copyright (C) 2010 Zubanov Dmitry
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.jssdb.core.query;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jssdb.core.DBProperties;
import org.jssdb.core.handler.Handler;
import org.jssdb.core.josql.OQLHandler;
import org.jssdb.core.josql.OQLParser;
import org.jssdb.core.ram.RamHandler;
import org.jssdb.core.utils.JSQLExeption;
import org.jssdb.core.utils.SizeException;

/**
 *
 * @author developer
 */
public class Query implements Serializable {

    private RamHandler ramHandler = RamHandler.getInstance();
    private Handler handler = Handler.getInstance();
    private int mode;
    private OQLHandler oqlHandler = OQLHandler.getInstance();
    private OQLParser oqlParser = OQLParser.getInstance();
    static final long serialVersionUID = 100000000000099002L;

    private Query() {
        mode = DBProperties.getInstance().getMemoryModel();
    }

    public long addObject(Object object) {
        long result = -1;
        if (mode == DBProperties.RAM) {
            result = ramHandler.addObject(object);
        }

        if (mode == DBProperties.STORAGE) {
            result = handler.add(object);
        }

        return result;
    }

    public boolean updateObject(long id, Object object) {
        boolean result = false;
        if (mode == DBProperties.RAM) {
            result = ramHandler.updateObject(id, object);
        }

        if (mode == DBProperties.STORAGE) {
            result = handler.update(id, object);
        }

        return result;
    }

    public boolean deleteObject(long id, Class cls) {
        boolean result = false;
        if (cls == null) {
            return result;
        }

        result = deleteObject(id, cls.getName());
        return result;
    }

    public boolean deleteObject(long id, Object object) {
        boolean result = false;
        if (object == null) {
            return result;
        }

        result = deleteObject(id, object.getClass().getName());
        return result;
    }

    public boolean deleteObject(long id, String className) {
        boolean result = false;
        if (mode == DBProperties.RAM) {
            result = ramHandler.deleteObject(id, className);
        }

        if (mode == DBProperties.STORAGE) {
            result = handler.delete(id, className);
        }

        return result;
    }

    public ArrayList<Map> getResultSet(String oqlRequest, boolean isNormalization){
        ArrayList<Map> resultSet = null;
        try {
            resultSet = oqlHandler.getResultSet(oqlParser.getParameters(oqlRequest), isNormalization);

        } catch (JSQLExeption ex) {
            Logger.getLogger(Query.class.getName()).log(Level.SEVERE, null, ex);
        }
        return resultSet;
    }

    public ArrayList<Map<Long, Object>> getResultSet(Request request) throws SizeException {
        ArrayList<Map<Long, Object>> resultSet = null;

        Class cls = request.getClazz();
        if (cls == null) {
            return resultSet;
        }

        // get all collection
        if (mode == DBProperties.RAM) {
            resultSet = ramHandler.getCollection(cls.getName(), true);
        }

        if (mode == DBProperties.STORAGE) {
            resultSet = handler.getCollection(cls);
        }

        if(resultSet == null){
            return resultSet;
        }

        if (request.getMethods() == null) {
            if (request.getSelectById() == null) {
                if (request.getLimit() == null) {
                    return resultSet;
                } else {
                    return handlerLimit(resultSet, request.getLimit());
                }

            } else {
                resultSet = handlerById(resultSet, request.getSelectById());
                if (request.getLimit() == null) {
                    return resultSet;
                } else {
                    return handlerLimit(resultSet, request.getLimit());
                }
            }

        } else {
            ArrayList<Map<Long, Object>> result = new ArrayList();
            for (Map<Long, Object> map : resultSet) {
                Object obj = map.get((Long) map.keySet().toArray()[0]);

                //Map<Method, Object[]> m = request.getMethods();
                Map<Method, Object[]> m = getMethods(request);
                Iterator it = m.keySet().iterator();

                boolean isAdd = false;
                ArrayList<Map<String, Object>> row = new ArrayList();
                while (it.hasNext()) {
                    Method method = (Method) it.next();
                    try {
                        Object value = method.invoke(obj, m.get(method));

                        if (request.getFilter() != null) {
                            //Map<Method, Expression> fMap = request.getFilter();
                            Map<Method, Expression> fMap = getFilter(request);
                            Iterator itFilter = fMap.keySet().iterator();
                            while (itFilter.hasNext()) {
                                Method fMethod = (Method) itFilter.next();
                                if (fMethod.equals(method)) {
                                    Expression expression = fMap.get(fMethod);
                                    isAdd = expression.compare(value);
                                }
                            }

                        } else {
                            isAdd = true;
                        }

                        if (!isAdd) {
                            continue;
                        }
                        Map<String, Object> mRow = new LinkedHashMap();
                        mRow.put(method.getName(), value);
                        row.add(mRow);

                    } catch (Exception ex) {
                        Logger.getLogger(Query.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }

                if (row.isEmpty()) {
                    continue;
                }

                // ################## WARNING !!! ################
                // this is model AND only
                // model OR is needed or no?
                // ################## WARNING !!! ################
                Map<Long, Object> mResult = new LinkedHashMap();
                if (row.size() == request.getMethods().keySet().size()) {
                    mResult.put((Long) map.keySet().toArray()[0], row);
                    result.add(mResult);
                }
            }

            resultSet.clear();
            resultSet = result;

            if (request.getSelectById() == null) {
                if (request.getLimit() == null) {
                    return resultSet;
                } else {
                    return handlerLimit(resultSet, request.getLimit());
                }

            } else {
                resultSet = handlerById(resultSet, request.getSelectById());
                if (request.getLimit() == null) {
                    return resultSet;
                } else {
                    return handlerLimit(resultSet, request.getLimit());
                }
            }
        }
    }

    private Map<Method, Object[]> getMethods(Request request) {
        LinkedHashMap<Method, Object[]> mm = new LinkedHashMap();
        if (request.getMethods() != null) {
            Iterator it = request.getMethods().keySet().iterator();
            while (it.hasNext()) {
                String methodName = "" + it.next();
                for (Method method : request.getClazz().getMethods()) {
                    if (method.getName().equals(methodName)) {
                        mm.put(method, request.getMethods().get(methodName));
                    }
                }
            }
        }

        return mm;
    }

    public Map<Method, Expression> getFilter(Request request) {
        LinkedHashMap<Method, Expression> mf = new LinkedHashMap();
        if (request.getFilter() != null) {
            Iterator it = request.getFilter().keySet().iterator();
            String methodName = "" + it.next();
            for (Method method : request.getClazz().getMethods()) {
                if (method.getName().equals(methodName)) {
                    mf.put(method, request.getFilter().get(methodName));
                }
            }
        }

        return mf;
    }

    private ArrayList<Map<Long, Object>> handlerById(ArrayList<Map<Long, Object>> list,
            SelectById expId) {
        ArrayList<Map<Long, Object>> result = new ArrayList();

        if (expId.getLeftExpression() == SelectById.EQUAL) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] == expId.getLeftValue()) {
                    result.add(m);
                }
            }
        }

        if (expId.getLeftExpression() == SelectById.GREATER && expId.getRightExpression() == -1) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] > expId.getLeftValue()) {
                    result.add(m);
                }
            }
        }

        if (expId.getLeftExpression() == SelectById.GREATER_OR_EQUAL && expId.getRightExpression() == -1) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] >= expId.getLeftValue()) {
                    result.add(m);
                }
            }
        }

        if (expId.getLeftExpression() == SelectById.LESS && expId.getRightExpression() == -1) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] < expId.getLeftValue()) {
                    result.add(m);
                }
            }
        }

        if (expId.getLeftExpression() == SelectById.LESS_OR_EQUAL && expId.getRightExpression() == -1) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] <= expId.getLeftValue()) {
                    result.add(m);
                }
            }
        }

        if (expId.getLeftExpression() == SelectById.GREATER
                && expId.getRightExpression() == SelectById.LESS) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] > expId.getLeftValue()
                        && (Long) m.keySet().toArray()[0] < expId.getRightValue()) {
                    result.add(m);
                }
            }
        }

        if (expId.getLeftExpression() == SelectById.GREATER_OR_EQUAL
                && expId.getRightExpression() == SelectById.LESS) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] >= expId.getLeftValue()
                        && (Long) m.keySet().toArray()[0] < expId.getRightValue()) {
                    result.add(m);
                }
            }
        }

        if (expId.getLeftExpression() == SelectById.GREATER_OR_EQUAL
                && expId.getRightExpression() == SelectById.LESS_OR_EQUAL) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] >= expId.getLeftValue()
                        && (Long) m.keySet().toArray()[0] <= expId.getRightValue()) {
                    result.add(m);
                }
            }
        }

        if (expId.getLeftExpression() == SelectById.GREATER
                && expId.getRightExpression() == SelectById.LESS_OR_EQUAL) {
            for (Map<Long, Object> m : list) {
                if ((Long) m.keySet().toArray()[0] > expId.getLeftValue()
                        && (Long) m.keySet().toArray()[0] <= expId.getRightValue()) {
                    result.add(m);
                }
            }
        }

        return result;
    }

    private ArrayList<Map<Long, Object>> handlerLimit(ArrayList<Map<Long, Object>> list,
            Limit limit) {
        ArrayList<Map<Long, Object>> result = new ArrayList();

        long count = 0;
        long len = 0;
        for (Map<Long, Object> map : list) {
            if (count >= limit.getFrom() && len < limit.getLen()) {
                result.add(map);
                len++;
            }
            count++;
        }
        return result;
    }

    public static Query getInstance() {
        return QueryHolder.INSTANCE;
    }

    private static class QueryHolder {

        private static final Query INSTANCE = new Query();
    }
}
